package com.wans.gateway.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wans.common.constans.CustomEnum;
import com.wans.common.constans.CustomExceptionEnum;
import com.wans.common.exception.CustomRunningException;
import com.wans.common.redis.RedisPrefix;
import com.wans.common.redis.RedisUtils;
import com.wans.common.result.ResponseResult;
import com.wans.model.user.rdtos.RedisSession;
import com.wans.utils.string.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Component
@Order(value = 1) // 优先级
@SuppressWarnings("all")
public class LoginAuthorizeFilter implements GlobalFilter {

    // 操作redis
    @Autowired
    private RedisUtils redisUtils;
    // 序列化类
    @Autowired
    private ObjectMapper objectMapper;


    /**
     * 请求过滤 - 检查是否登录
     *
     * @param exchange：这个标签用于说明方法参数exchange的含义和作用。 <br/>
     *                                             exchange 是 ServerWebExchange 类型的对象，它封装了HTTP请求和响应的上下文，<br/>
     *                                             提供了访问请求、响应以及一些其他属性（如session、请求属性等）的方法。<br/>
     *                                             在网关过滤器中，我们可以通过它来获取请求信息、修改请求或响应等。<br/>
     * @param chain：这个标签用于说明方法参数chain的含义和作用。<br/>  chain 是 GatewayFilterChain 类型的对象，<br/>
     *                                             代表过滤器链。通过调用chain.filter(exchange)可以将请求传递给下一个过滤器（最终会路由到目标服务）。<br/>
     *                                             如果我们不想继续传递请求（比如因为未授权而拦截），则可以不调用这个方法，而是直接返回一个设置好响应的Mono<Void>。<br/>
     * @return 这个标签用于说明方法的返回值。 该方法返回一个Mono<Void>。<br/>
     * 在响应式编程中，Mono代表一个异步操作，这个操作在完成时不会返回具体的数据（因此是Void）。<br/>
     * 返回的Mono可以表示两种结果：<br/>
     * a. 请求被正常传递并处理（调用了chain.filter(exchange)返回的Mono）。<br/>
     * b. 请求被拦截，我们直接操作了响应（例如设置了状态码并调用exchange.getResponse().setComplete()）并返回相应的Mono。<br/>
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 获取request和response对象
        ServerHttpRequest request = exchange.getRequest();

        // 判断是否是登录、注册
        String path = request.getURI().getPath();
        MultiValueMap<String, String> queryParams = request.getQueryParams();
        if ("/user/login".equals(path) || "/user/register".equals(path)) {
            //放行
            if (queryParams == null || queryParams.isEmpty() || queryParams.size() == 0)
                // 没有查询参数
                return chain.filter(exchange);
        }


        // 校验是否登录
        String userId = needAuthorizeCheckRequest(exchange.getRequest());
        if (!StringUtils.isContainEmpty(userId)) {
            // 通行的加上请求头
            exchange.mutate()
                    .request(request.mutate().header("X-User-ID", userId).build())  // 自定义用户头
                    .build();
            // 通行
            return chain.filter(exchange);
        }

        // 不能通行 - 获取响应
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);

        // 响应体
        // 使用缓冲区工厂创建数据缓冲区
        DataBufferFactory dataBufferFactory = response.bufferFactory();
        byte[] bytes = null;
        try {
            bytes = objectMapper.writeValueAsString(ResponseResult.fail(
                    CustomExceptionEnum.USER_NOT_LOGIN.getErrorCode(),
                    CustomExceptionEnum.USER_NOT_LOGIN.getErrorMessage())).getBytes(StandardCharsets.UTF_8);
        } catch (JsonProcessingException e) {
            // 序列化失败
            throw new CustomRunningException(CustomExceptionEnum.JSON_PROCESS_EX);
        }
        DataBuffer buffer = dataBufferFactory.wrap(bytes);

        // 返回带响应体的完成信号

        return response.writeWith(Mono.just(buffer));
    }


    /**
     * 登录权限校验 - 检查是否登录
     *
     * @param request 请求数据
     * @return 登录着的用户 Id
     */
    public String needAuthorizeCheckRequest(ServerHttpRequest request) {

        // 从请求中获取 cookie
        HttpCookie cookie = request.getCookies().getFirst(CustomEnum.LOGIN_COOKIE_NAME.getValue());
        if (cookie == null)
            // 访问需要登录的资源时请求没有cookie
            return "";


        // 长缓存 key - user:cookie:login:long:UUID
        String redisLongKey = RedisPrefix.USER_COOKIE_LONG_NAME_PREFIX.getPrefix() + cookie.getValue();


        try {
            // 获取长缓存里的值 - id
            String redisLongValue = redisUtils.get(redisLongKey);
            if (StringUtils.isContainEmpty(redisLongValue))
                // 无法获取该 key 的值 - 没有这个登录的会话
                return "";

            // 获取短缓存里的值
            String redisShortKey = RedisPrefix.USER_COOKIE_SHORT_NAME_PREFIX.getPrefix() + redisLongValue;
            String redisShortValue = null;

            // 其他短缓存
            List<String> otherRedisShortValues = new LinkedList<>();
            for (String value : redisUtils.lRange(redisShortKey, 0, -1)) {

                if (value.contains(cookie.getValue())) {
                    // 包含 cookie 的值-value 找的就是它
                    redisShortValue = value;
                    continue;
                }

                // 其他数据可以进行加入 otherRedisShortValues
                otherRedisShortValues.add(value);

                //TODO 后期可以对其他逻辑过期的 value 进行清除
            }
            if (StringUtils.isContainEmpty(redisShortValue))
                // 无法获取该 key 的值 - 短会话可能已经过期 - 一样提示用户重新登录
                return "";

            // 对值进行序列话做进一步校验 - 短缓存里的值序列化为RedisSession - 已经过滤其他设备的登录值
            RedisSession redisShortSession = objectMapper.readValue(redisShortValue, RedisSession.class);
            // 校验过期时间
            if (System.currentTimeMillis() < redisShortSession.getExpire() &&
                    System.currentTimeMillis() + TimeUnit.HOURS.toMillis(12) > redisShortSession.getExpire()) {
                // 没有过期并且 有效期 只剩余 12 小时以内
                // 刷新时间 - 重置为24小时
                redisShortSession.setExpire(System.currentTimeMillis() +
                        TimeUnit.HOURS.toMillis(Integer.parseInt(CustomEnum.REDIS_SHORT_HOURS.getValue())));
                // 短缓存有效期已经不足 12 小时 - 刷新逻辑过期时间
                otherRedisShortValues.add(objectMapper.writeValueAsString(redisShortSession));
                redisUtils.modifyList(redisShortKey, otherRedisShortValues);

                return redisLongValue;
            }
            if (System.currentTimeMillis() < redisShortSession.getExpire()) {
                // 此时 说明 有效期大于 12 小时 - 无需刷新时间 - 直接添加原本的短会话 - 无需进行任何更改
                //otherRedisShortValues.add(objectMapper.writeValueAsString(redisShortSession));
                return redisLongValue;
            }
            if (System.currentTimeMillis() > redisShortSession.getExpire()) {
                // 该会话已经过期 - 删除存在的 会话（长会话和短会话的值）
                redisUtils.delete(redisLongKey);

                // 这里相当于去掉了过期的 value
                redisUtils.modifyList(redisShortKey, otherRedisShortValues);

                // 失败错误
                return "";
            }


        } catch (JsonProcessingException e) {
            throw new CustomRunningException(CustomExceptionEnum.JSON_PROCESS_EX, e);
        } catch (RuntimeException e) {
            throw new CustomRunningException(CustomExceptionEnum.Redis_PROCESS_EX, e);
        } catch (Exception e) {
            throw new CustomRunningException(CustomExceptionEnum.OTHER_PROCESS_EX, e);
        }

        // 可以通行
        // 大概率运行不到这里
        System.err.println("短会话运行ing - 逻辑出现错误");
        return "";
    }
}
